/*
 *  FILE: vfs_syscall.c
 *  AUTH: mcc | jal
 *  DESC:
 *  DATE: Wed Apr  8 02:46:19 1998
 *  $Id: vfs_syscall.c,v 1.1 2012/10/10 20:06:46 william Exp $
 */

#include "kernel.h"
#include "errno.h"
#include "globals.h"
#include "fs/vfs.h"
#include "fs/file.h"
#include "fs/vnode.h"
#include "fs/vfs_syscall.h"
#include "fs/open.h"
#include "fs/fcntl.h"
#include "fs/lseek.h"
#include "mm/kmalloc.h"
#include "util/string.h"
#include "util/printf.h"
#include "fs/stat.h"
#include "util/debug.h"

/* To read a file:
 *      o fget(fd)
 *      o call its virtual read f_op
 *      o update f_pos
 *      o fput() it
 *      o return the number of bytes read, or an error
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EBADF
 *        fd is not a valid file descriptor or is not open for reading.
 *      o EISDIR
 *        fd refers to a directory.
 *
 * In all cases, be sure you do not leak file refcounts by returning before
 * you fput() a file that you fget()'ed.
 */
int
do_read(int fd, void *buf, size_t nbytes)
{
/* Yu code below v0.9 */
    file_t *file_table_entry;
    vnode_t *file_vnode;
    int nread_bytes;

    if (fd == -1) {
	    return -EBADF;
    }

    /* verify the fd and reading right  */
    if ( file_table_entry = fget( fd ), file_table_entry == NULL ||
        !(file_table_entry->f_mode & FMODE_READ) ) {
        /* clean up before return */
        if ( file_table_entry != NULL ) {
            fput( file_table_entry );
        }
        return -EBADF;
    }

    /* verify whether is a directory */
    file_vnode = file_table_entry->f_vnode;
    if ( S_ISDIR(file_vnode->vn_mode) ) {
    	fput( file_table_entry );
        return -EISDIR;
    }

    /* call the actual fs read method */
    nread_bytes = file_vnode->vn_ops->read ( file_vnode, file_table_entry->f_pos, buf, nbytes );
    file_table_entry->f_pos += nread_bytes;
    fput( file_table_entry );

    return nread_bytes;
/* Yu code above v0.9 */
}

/* Very similar to do_read.  Check f_mode to be sure the file is writable.  If
 * f_mode & FMODE_APPEND, do_lseek() to the end of the file, call the write
 * f_op, and fput the file.  As always, be mindful of refcount leaks.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EBADF
 *        fd is not a valid file descriptor or is not open for writing.
 */
int
do_write(int fd, const void *buf, size_t nbytes)
{
/* Yu code below v0.9 */
    file_t *file_table_entry;
    vnode_t *file_vnode;
    int nwrite_bytes;

    if (fd == -1) {
	    return -EBADF;
    }

    /* verify the fd and writing right  */
    if ( file_table_entry = fget( fd ), file_table_entry == NULL ||
        !(file_table_entry->f_mode & FMODE_WRITE) ) {
        	/* clean up before return */
        	if ( file_table_entry != NULL ) {
        	   fput( file_table_entry );
        }
        return -EBADF;
    }

    /* set f_pos */
    if ( (file_table_entry->f_mode) & FMODE_APPEND ) {
        do_lseek( fd, 0, SEEK_END );
    }

    /* call actual write func */
    file_vnode = file_table_entry->f_vnode;
    if (file_table_entry->f_pos > file_vnode->vn_len) {
    }
    nwrite_bytes = file_vnode->vn_ops->write(file_vnode,
                                              file_table_entry->f_pos,
                                              buf,
                                              nbytes);
	if (nwrite_bytes >= 0)
		KASSERT((S_ISCHR(file_table_entry->f_vnode->vn_mode)) ||
            		(S_ISBLK(file_table_entry->f_vnode->vn_mode)) ||
           		((S_ISREG(file_table_entry->f_vnode->vn_mode)) && (file_table_entry->f_pos <= file_table_entry->f_vnode->vn_len)));
    /* update f_pos */
    if (nwrite_bytes >= 0) file_table_entry->f_pos += nwrite_bytes;

    /* fput */
    fput( file_table_entry );

    return nwrite_bytes;
/* Yu code above v0.9 */
}

/*
 * Zero curproc->p_files[fd], and fput() the file. Return 0 on success
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EBADF
 *        fd isn't a valid open file descriptor.
 */
int
do_close(int fd)
{
/* Yu code below v0.9 */
    file_t *file_table_entry;

    /* verify the fd */
    if (fd == -1)
	    return -EBADF;

    file_table_entry = fget( fd );
    if (file_table_entry == NULL ) {
        return -EBADF;
    }

    /* zero fd table */
    curproc->p_files[fd] = NULL;

    /* fput twice */
    fput( file_table_entry ); /* for fget in verify process */
    fput( file_table_entry ); /* actual fput the file */

    return 0;
/* Yu code above v0.9 */
}

/* To dup a file:
 *      o fget(fd) to up fd's refcount
 *      o get_empty_fd()
 *      o point the new fd to the same file_t* as the given fd
 *      o return the new file descriptor
 *
 * Don't fput() the fd unless something goes wrong.  Since we are creating
 * another reference to the file_t*, we want to up the refcount.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EBADF
 *        fd isn't an open file descriptor.
 *      o EMFILE
 *        The process already has the maximum number of file descriptors open
 *        and tried to open a new one.
 */
int
do_dup(int fd)
{
/* Yu code below v0.9 */
    file_t *file_table_entry;
    int new_fd;

    /* verify the fd */
    if (fd == -1) {
	    return -EBADF;
    }

    file_table_entry = fget( fd );
    if ( file_table_entry == NULL ) {
        return -EBADF;
    }

    /* get a new fd */
    new_fd = get_empty_fd(curproc);
    if ( new_fd == -EMFILE ){
        fput( file_table_entry );
        return new_fd;
    }

    /* set the new fd */
    curproc->p_files[new_fd] = file_table_entry;

    return new_fd;
/* Yu code above v0.9 */
}

/* Same as do_dup, but insted of using get_empty_fd() to get the new fd,
 * they give it to us in 'nfd'.  If nfd is in use (and not the same as ofd)
 * do_close() it first.  Then return the new file descriptor.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EBADF
 *        ofd isn't an open file descriptor, or nfd is out of the allowed
 *        range for file descriptors.
 */
int
do_dup2(int ofd, int nfd)
{
/* Yu code below v1.0 */
    int ret = 0;
    file_t *ofd_file_table_entry;
    file_t *nfd_file_table_entry;
    
    if ((ofd >= NFILES) || (ofd < 0)) {
	    return -EBADF;
    }
    if ((nfd >= NFILES) || (nfd < 0)) {
	    return -EBADF;
    }

    /* verify the fds */
    ofd_file_table_entry = fget( ofd );
    if ( ofd_file_table_entry == NULL ) {
        return -EBADF;
    }
    
    if (nfd == ofd) {
        fput(ofd_file_table_entry);
	    return ofd;
    }

    /* close nfd if in use */
    if ( curproc->p_files[nfd] != NULL ) {
        ret = do_close(nfd);
    }
    if (ret < 0) {
    	fput(ofd_file_table_entry);
	return ret;
    }

    /* set the new fd */
    curproc->p_files[nfd] = ofd_file_table_entry;
    
    return nfd;
/* Yu code above v1.0 */
}

/*
 * This routine creates a special file of the type specified by 'mode' at
 * the location specified by 'path'. 'mode' should be one of S_IFCHR or
 * S_IFBLK (you might note that mknod(2) normally allows one to create
 * regular files as well-- for simplicity this is not the case in Weenix).
 * 'devid', as you might expect, is the device identifier of the device
 * that the new special file should represent.
 *
 * You might use a combination of dir_namev, lookup, and the fs-specific
 * mknod (that is, the containing directory's 'mknod' vnode operation).
 * Return the result of the fs-specific mknod, or an error.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EINVAL
 *        mode requested creation of something other than a device special
 *        file.
 *      o EEXIST
 *        path already exists.
 *      o ENOENT
 *        A directory component in path does not exist.
 *      o ENOTDIR
 *        A component used as a directory in path is not, in fact, a directory.
 *      o ENAMETOOLONG
 *        A component of path was too long.
 */
int
do_mknod(const char *path, int mode, unsigned devid)
{
/* Yu code below v0.9 */
    char *path_piece_ptr;
    size_t pathname_len;
    int path_piece_len;

    vnode_t *parent_vnode;
    vnode_t *new_dev_vnode;
    char *new_dev_name;
    size_t new_dev_name_len;

    int retval;

    /* verify the pathname length */
    if (path == NULL) {
	    return -EINVAL;
    }
    pathname_len = strlen(path);
    if( pathname_len > MAXPATHLEN) {
        return -ENAMETOOLONG;
    }
    if (pathname_len < 1) {
	    return -EINVAL;
    }

    /* verify whether try to create root */
    if( pathname_len == 1 && path[0]=='/' ) {
        return -EEXIST;
    }
    
    /* verify the pathname format */
    /* unlike the standard UNIX, all characters in pathname must be printable here */
    if( path[0] == '/' ){
        path_piece_ptr = (char*)path+1;
    } else {
        path_piece_ptr = (char*)path;
    }
    for( path_piece_len=0 ;
         *path_piece_ptr != '\0';
         path_piece_ptr++  ){
        if( *path_piece_ptr != '/' ) {
            path_piece_len ++;
            if (path_piece_len > NAME_LEN ) {
                return -ENAMETOOLONG;
            }
        } else {
            path_piece_len = 0;
    	}
    }
    
    /* verify mode */
    if( !S_ISCHR(mode) && !S_ISBLK(mode) ){
        return -EINVAL;
    }
    
    /* get the parent vnode */
    retval = dir_namev(path, &new_dev_name_len,(const char**)&new_dev_name,
                       NULL, &parent_vnode );
    if( retval != 0 ) {
        return retval;
    }
    
    /* verify whether the new dev already exist */
    retval = lookup(parent_vnode, new_dev_name, new_dev_name_len, &new_dev_vnode);
    if(retval != -ENOENT) {
        kfree( new_dev_name );
        vput( parent_vnode );
        if( retval == 0 ) {
            vput(new_dev_vnode);
            return -EEXIST;
        }
        return retval;/*-ENOTDIR*/
    }
    
    /* call the actual mknod func */
    KASSERT(NULL != parent_vnode->vn_ops->mknod);
    retval = parent_vnode->vn_ops->mknod(parent_vnode, 
                                         new_dev_name,
                                         new_dev_name_len,
                                         mode,
                                         (devid_t)devid);
    vput( parent_vnode );
    kfree(new_dev_name);
    return retval;
/* Yu code above v0.9 */
}

/* Use dir_namev() to find the vnode of the dir we want to make the new
 * directory in.  Then use lookup() to make sure it doesn't already exist.
 * Finally call the dir's mkdir vn_ops. Return what it returns.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EEXIST
 *        path already exists.
 *      o ENOENT
 *        A directory component in path does not exist.
 *      o ENOTDIR
 *        A component used as a directory in path is not, in fact, a directory.
 *      o ENAMETOOLONG
 *        A component of path was too long.
 */
int
do_mkdir(const char *path)
{
/* Yu code below v1.0.0 */
    char *path_piece_ptr;
    size_t pathname_len;
    int path_piece_len;

    vnode_t *parent_vnode;
    vnode_t *new_dir_vnode;
    char *new_dir_name;
    size_t new_dir_name_len;

    int retval;

    /* verify the pathname length */
    if (path == NULL) {
	return -EINVAL;
    }
    pathname_len = strlen(path);
    if( pathname_len > MAXPATHLEN) {
        return -ENAMETOOLONG;
    }
    if (pathname_len < 1) {
	return -EINVAL;
    }

    /* verify whether try to create root */
    if( pathname_len == 1 && path[0]=='/' ) {
        return -EEXIST;
    }

    /* verify the pathname format */
    /* unlike the standard UNIX, all characters in pathname must be printable here */
    if( path[0] == '/' ){
        path_piece_ptr = (char*)path+1;
    } else {
        path_piece_ptr = (char*)path;
    }
    
    for( path_piece_len=0 ;
         *path_piece_ptr != '\0';
         path_piece_ptr++  ){
    	    if( *path_piece_ptr != '/' ) {
    	        path_piece_len ++;
    	        if (path_piece_len > NAME_LEN ) {
    	            return -ENAMETOOLONG;
    	        }
    	    } else {
    	    	path_piece_len = 0;
    	    }
    	}

    /* get parent vnode */
    retval = dir_namev(path, &new_dir_name_len,(const char**)&new_dir_name,
                       NULL, &parent_vnode );
    if( retval != 0 ) {
        return retval;
    }
    /* verify whether the new dir already exist */
    retval = lookup(parent_vnode, new_dir_name, new_dir_name_len, &new_dir_vnode);
    if(retval != -ENOENT) {
        kfree( new_dir_name );
        vput( parent_vnode );
        if( retval == 0 ) {
            vput(new_dir_vnode);
            return -EEXIST;
        }
        return retval;/*-ENOTDIR*/
    }

    /* call actual mkdir func */
    KASSERT(NULL != parent_vnode->vn_ops->mkdir);
    retval = parent_vnode->vn_ops->mkdir(parent_vnode, new_dir_name, new_dir_name_len);
    vput( parent_vnode );
    kfree(new_dir_name);
    return retval;
/* Yu code above v1.0.0 */
}

/* Use dir_namev() to find the vnode of the directory containing the dir to be
 * removed. Then call the containing dir's rmdir v_op.  The rmdir v_op will
 * return an error if the dir to be removed does not exist or is not empty, so
 * you don't need to worry about that here. Return the value of the v_op,
 * or an error.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EINVAL
 *        path has "." as its final component.
 *      o ENOTEMPTY
 *        path has ".." as its final component.
 *      o ENOENT
 *        A directory component in path does not exist.
 *      o ENOTDIR
 *        A component used as a directory in path is not, in fact, a directory.
 *      o ENAMETOOLONG
 *        A component of path was too long.
 */
int
do_rmdir(const char *path)
{
/* Yu code below v0.9 */
    char *path_piece_ptr;
    size_t pathname_len;
    int path_piece_len;

    vnode_t *parent_vnode;
    char *rm_dir_name;
    size_t rm_dir_name_len;

    int retval;

    /* verify the pathname length */
    if (path == NULL) {
	return -EINVAL;
    }
    pathname_len = strlen(path);
    if( pathname_len > MAXPATHLEN) {
        return -ENAMETOOLONG;
    }
    if (pathname_len < 1) {
	return -EINVAL;
    }

    /* verify the pathname format */
    /* unlike the standard UNIX, all characters in pathname must be printable here */
    if( path[0] == '/' ){
        path_piece_ptr = (char*)path+1;
    } else {
        path_piece_ptr = (char*)path;
    }
    for( path_piece_len=0 ;
         *path_piece_ptr != '\0';
         path_piece_ptr++  ){
    	    if( *path_piece_ptr != '/' ) {
    	        path_piece_len ++;
    	        if (path_piece_len > NAME_LEN ) {
    	            return -ENAMETOOLONG;
    	        }
    	    } else {
    	    	path_piece_len = 0;
    	    }
    }

    /* get parent vnode */
    retval = dir_namev(path, &rm_dir_name_len, (const char**)&rm_dir_name,
                       NULL, &parent_vnode );
    if( retval != 0 ) {
        return retval;
    }
    /* verify the parent vnode is dir */
    if (!S_ISDIR(parent_vnode->vn_mode)) {
        vput( parent_vnode );
        kfree( rm_dir_name );
        return -ENOTDIR;
    }

    /* verify the finel component */
    if ( strcmp(rm_dir_name, ".") == 0 ) {
        vput( parent_vnode );
        kfree( rm_dir_name );
        return -EINVAL;
    }
    if ( strcmp(rm_dir_name, "..") == 0 ) {
        vput( parent_vnode );
        kfree( rm_dir_name );
        return -ENOTEMPTY;
    }

    /* call the actual rmdir */
    KASSERT(NULL != parent_vnode->vn_ops->rmdir);
    retval = parent_vnode->vn_ops->rmdir( parent_vnode,
                                           rm_dir_name,
                                           rm_dir_name_len);
    vput( parent_vnode );
    kfree( rm_dir_name );
    return retval;
/* Yu code above v0.9 */
}

/*
 * Same as do_rmdir, but for files.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EISDIR
 *        path refers to a directory.
 *      o ENOENT
 *        A component in path does not exist.
 *      o ENOTDIR
 *        A component used as a directory in path is not, in fact, a directory.
 *      o ENAMETOOLONG
 *        A component of path was too long.
 */
int
do_unlink(const char *path)
{
/* Yu code below v0.9 */
    char *path_piece_ptr;
    size_t pathname_len;
    int path_piece_len;

    vnode_t *parent_vnode;
    vnode_t *unl_file_vnode;
    char *unl_file_name;
    size_t unl_file_name_len;

    int retval;

    /* verify the pathname length */
    if (path == NULL) {
	return -EINVAL;
    }
    pathname_len = strlen(path);
    if( pathname_len > MAXPATHLEN) {
        return -ENAMETOOLONG;
    }
    if (pathname_len < 1) {
	return -EINVAL;
    }

    /* verify the pathname format */
    /* unlike the standard UNIX, all characters in pathname must be printable here */
    if( path[0] == '/' ){
        path_piece_ptr = (char*)path+1;
    } else {
        path_piece_ptr = (char*)path;
    }
    for( path_piece_len=0 ;
         *path_piece_ptr != '\0';
         path_piece_ptr++  ){
    	    if( *path_piece_ptr != '/' ) {
    	        path_piece_len ++;
    	        if (path_piece_len > NAME_LEN ) {
    	            return -ENAMETOOLONG;
    	        }
    	    } else {
    	        path_piece_len = 0;
    	    }
    }

    /* get parent vnode */
    retval = dir_namev(path, &unl_file_name_len, (const char**)&unl_file_name,
                       NULL, &parent_vnode );
    if( retval != 0 ) {
        return retval;
    }

    /* verify the final elem */
    retval = lookup( parent_vnode,
                     unl_file_name,
                     unl_file_name_len,
                     &unl_file_vnode);
    if( retval != 0 ) {
        vput(parent_vnode);
        kfree( unl_file_name );
        return retval; /* -ENOENT or -ENOTDIR */
    }
    if( S_ISDIR(unl_file_vnode->vn_mode) ){
        vput(parent_vnode);
        vput(unl_file_vnode);
        kfree( unl_file_name );
        return -EISDIR;
    }
    vput(unl_file_vnode);

    /* call the actual unlink */
    KASSERT(NULL != parent_vnode->vn_ops->unlink);
    retval = parent_vnode->vn_ops->unlink( parent_vnode,
                                            unl_file_name,
                                            unl_file_name_len);
    vput(parent_vnode);
    kfree( unl_file_name );
    return retval;
/* Yu code above v0.9 */
}

/* To link:
 *      o open_namev(from) (oldpath)
 *      o dir_namev(to) (newpath, if exist already, not overwriten)
 *      o call the destination dir's (to) link vn_ops.
 *      o return the result of link, or an error
 *
 * Remember to vput the vnodes returned from open_namev and dir_namev.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EEXIST
 *        to already exists.
 *      o ENOENT
 *        A directory component in from or to does not exist.
 *      o ENOTDIR
 *        A component used as a directory in from or to is not, in fact, a
 *        directory.
 *      o ENAMETOOLONG
 *        A component of from or to was too long.
 */
int
do_link(const char *from, const char *to)
{
/* Yu code below v0.9 */
    char *path_piece_ptr;
    size_t pathname_len;
    int path_piece_len;

    vnode_t *old_path_vnode;
    vnode_t *parent_vnode;
    vnode_t *new_link_vnode;
    char *new_link_name;
    size_t new_link_name_len;

    int retval;

    /* verify the from pathname length */
    if ((from == NULL) || (to == NULL)) {
	return -EINVAL;
    }
    pathname_len = strlen(from);
    if( pathname_len > MAXPATHLEN) {
        return -ENAMETOOLONG;
    }
    if (pathname_len < 1) {
	return -EINVAL;
    }

    /* verify the from pathname format */
    /* unlike the standard UNIX, all characters in pathname must be printable here */
    if( from[0] == '/' ){
        path_piece_ptr = (char*)from+1;
    } else {
        path_piece_ptr = (char*)from;
    }
    for( path_piece_len=0 ;
         *path_piece_ptr != '\0';
         path_piece_ptr++  ){
    	    if( *path_piece_ptr != '/' ) {
    	        path_piece_len ++;
    	        if (path_piece_len > NAME_LEN ) {
    	            return -ENAMETOOLONG;
    	        }
    	    } else {
    	    	path_piece_len = 0;
    	    }
    }

    /* verify the to pathname length */
    pathname_len = strlen(to);
    if( pathname_len > MAXPATHLEN) {
        return -ENAMETOOLONG;
    }
    if (pathname_len < 1) {
	return -EINVAL;
    }

    /* verify the to pathname format */
    /* unlike the standard UNIX, all characters in pathname must be printable here */
    if( to[0] == '/' ){
        path_piece_ptr = (char*)to+1;
    } else {
        path_piece_ptr = (char*)to;
    }
    for( path_piece_len=0 ;
         *path_piece_ptr != '\0';
         path_piece_ptr++  ){
    	    if( *path_piece_ptr != '/' ) {
    	        path_piece_len ++;
    	        if (path_piece_len > NAME_LEN ) {
    	            return -ENAMETOOLONG;
    	        }
    	    } else {
    	    	 path_piece_len = 0;
    	    }
    }

    /* get the oldpath(from) vnode */
    retval = open_namev( from,
                         NULL,
                         &old_path_vnode,
                         NULL);
    if( retval != 0 ) {
        return retval;
    }

    /* get new link's parent vnode */
    retval = dir_namev(to, &new_link_name_len, (const char**)&new_link_name,
                       NULL, &parent_vnode );
    if( retval != 0 ) {
        vput(old_path_vnode);
        return retval;
    }

    /* verify new link vnode's existance */
    retval = lookup( parent_vnode,
                     new_link_name,
                     new_link_name_len,
                     &new_link_vnode);
    if( retval != -ENOENT ) {
        vput(old_path_vnode);
        vput(parent_vnode);
        kfree( new_link_name );
        if( retval == 0 ) {
            vput(new_link_vnode);
            return -EEXIST;
        }
        return retval; /* -ENOTDIR */
    }

    /* call the actual link */
    retval = parent_vnode->vn_ops->link(old_path_vnode,
                                         parent_vnode,
                                         new_link_name,
                                         new_link_name_len);
    vput(old_path_vnode);
    vput(parent_vnode);
    kfree( new_link_name );
    return retval;
/* Yu code above v0.9 */
}

/*      o link newname to oldname
 *      o unlink oldname
 *      o return the value of unlink, or an error
 *
 * Note that this does not provide the same behavior as the
 * Linux system call (if unlink fails then two links to the
 * file could exist).
 */
int
do_rename(const char *oldname, const char *newname)
{
/* Yu code below v0.9 */
    int retval;
    if ((oldname == NULL) || (newname == NULL)) {
	    return -EINVAL;
    }
    if ((strlen(oldname) < 1) || ((strlen(newname) <1))) {
	    return -EINVAL;
    }

    retval = do_link(oldname, newname);
    if( retval != 0 ){
        return retval;
    }
    retval = do_unlink(oldname);
    return retval;
/* Yu code above v0.9 */
}

/* Make the named directory the current process's cwd (current working
 * directory).  Don't forget to down the refcount to the old cwd (vput()) and
 * up the refcount to the new cwd (open_namev() or vget()). Return 0 on
 * success.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o ENOENT
 *        path does not exist.
 *      o ENAMETOOLONG
 *        A component of path was too long.
 *      o ENOTDIR
 *        A component of path is not a directory.
 */
int
do_chdir(const char *path)
{
/* Yu code below v0.9 */
    char *path_piece_ptr;
    size_t pathname_len;
    int path_piece_len;

    vnode_t *new_dir_vnode;

    int retval;

    /* verify the pathname length */
    if (path == NULL) {
	return -EINVAL;
    }
    pathname_len = strlen(path);
    if( pathname_len > MAXPATHLEN) {
        return -ENAMETOOLONG;
    }
    if (pathname_len < 1) {
	return -EINVAL;
    }

    /* verify the pathname format */
    /* unlike the standard UNIX, all characters in pathname must be printable here */
    if( path[0] == '/' ){
        path_piece_ptr = (char*)path+1;
    } else {
        path_piece_ptr = (char*)path;
    }
    for( path_piece_len=0 ;
         *path_piece_ptr != '\0';
         path_piece_ptr++  ){
    	    if( *path_piece_ptr != '/' ) {
    	        path_piece_len ++;
    	        if (path_piece_len > NAME_LEN ) {
    	            return -ENAMETOOLONG;
    	        }
    	    } else {
    	    	 path_piece_len = 0;
    	    }
    }

    /* get new dir vnode */
    retval = open_namev(path,
                        NULL,
                        &new_dir_vnode,
                        NULL);
    if( retval != 0 ) {
        return retval;
    }

    /* verify new dir vnode */
    if ( !S_ISDIR(new_dir_vnode->vn_mode) ) {
        vput(new_dir_vnode);
        return -ENOTDIR;
    }

    /* change cwd */
    vput(curproc->p_cwd);
    curproc->p_cwd = new_dir_vnode;
    return 0;
/* Yu code above v0.9 */
}

/* Call the readdir f_op on the given fd, filling in the given dirent_t*.
 * If the readdir f_op is successful, it will return a positive value which
 * is the number of bytes copied to the dirent_t.  You need to increment the
 * file_t's f_pos by this amount.  As always, be aware of refcounts, check
 * the return value of the fget and the virtual function, and be sure the
 * virtual function exists (is not null) before calling it.
 *
 * Return either 0 or sizeof(dirent_t), or -errno.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EBADF
 *        Invalid file descriptor fd.
 *      o ENOTDIR
 *        File descriptor does not refer to a directory.
 */
int
do_getdent(int fd, struct dirent *dirp)
{
/* Yu code below v0.9 */
    file_t *file_table_entry;
    vnode_t *file_vnode;
    int retval;
    
    if (fd == -1) {
	    return -EBADF;
    }

    /* verify the fd */
    if ( file_table_entry = fget( fd ), file_table_entry == NULL ||
        !(file_table_entry->f_mode & FMODE_READ) ) {
        if ( file_table_entry != NULL ) {
            fput( file_table_entry );
        }
        return -EBADF;
    }

    /* verify whether fd is a dir */
    file_vnode = file_table_entry->f_vnode;
    if ( !S_ISDIR(file_vnode->vn_mode) ) {
    	fput( file_table_entry );
        return -ENOTDIR;
    }

    /* call actual readdir func */
    retval = file_vnode->vn_ops->readdir(file_vnode,
                                         file_table_entry->f_pos,
                                         dirp);
    if (retval >= 0) {
    	file_table_entry->f_pos += retval;
    }
    fput(file_table_entry);
    if (retval <= 0) {
    	return retval;
    }
    else {
    	return sizeof(dirent_t);
    }
/* Yu code above v0.9 */
}

/*
 * Modify f_pos according to offset and whence.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EBADF
 *        fd is not an open file descriptor.
 *      o EINVAL
 *        whence is not one of SEEK_SET, SEEK_CUR, SEEK_END; or the resulting
 *        file offset would be negative.
 */
int
do_lseek(int fd, int offset, int whence)
{
 /* Yu code below v1.0 */
    file_t *file_table_entry;
    int old_pos;
    int retval;

    if (fd == -1) {
	    return -EBADF;
    }

    /* verify the fd */
    if ( file_table_entry = fget( fd ), file_table_entry == NULL ) {
        return -EBADF;
    }

    /* verify arg whence */
    if( whence != SEEK_SET && whence != SEEK_CUR && whence!=SEEK_END ) {
        fput( file_table_entry );
        return -EINVAL;
    }

    /* set f_pos  */
    old_pos = file_table_entry->f_pos;
    switch (whence) {
        case SEEK_SET:
            file_table_entry->f_pos = offset;
            break;
        case SEEK_CUR:
            file_table_entry->f_pos += offset;
            break;
        case SEEK_END:
            file_table_entry->f_pos = file_table_entry->f_vnode->vn_len +
                                      offset;
            break;
        default:
            fput( file_table_entry );
            return -EINVAL;
    }
    if ( file_table_entry->f_pos < 0 ){
        file_table_entry->f_pos = old_pos;
        fput( file_table_entry );
        return -EINVAL;
    }
    
    retval = file_table_entry->f_pos;
    fput( file_table_entry );
    return retval;
/* Yu code above v1.0 */
}

/*
 * Find the vnode associated with the path, and call the stat() vnode operation.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o ENOENT
 *        A component of path does not exist.
 *      o ENOTDIR
 *        A component of the path prefix of path is not a directory.
 *      o ENAMETOOLONG
 *        A component of path was too long.
 */
int
do_stat(const char *path, struct stat *buf)
{
/* Yu code below v0.9 */
    char *path_piece_ptr;
    size_t pathname_len;
    int path_piece_len;

    vnode_t *file_vnode;

    int retval;

    /* verify the pathname length */
    if (path == NULL||buf ==NULL) {
	return -EINVAL;
    }
    pathname_len = strlen(path);
    if( pathname_len > MAXPATHLEN) {
        return -ENAMETOOLONG;
    }
    if (pathname_len < 1) {
	return -EINVAL;
    }

    /* verify the pathname format */
    /* unlike the standard UNIX, all characters in pathname must be printable here */
    if( path[0] == '/' ){
        path_piece_ptr = (char*)path+1;
    } else {
        path_piece_ptr = (char*)path;
    }
    for( path_piece_len=0 ;
         *path_piece_ptr != '\0';
         path_piece_ptr++  ){
    	    if( *path_piece_ptr != '/' ) {
    	        path_piece_len ++;
    	        if (path_piece_len > NAME_LEN ) {
    	            return -ENAMETOOLONG;
    	        }
    	    } else {
    	    	 path_piece_len = 0;
    	    }
    }

    /* get the file vnode */
    retval = open_namev(path,
                        NULL,
                        &file_vnode,
                        NULL);
    if( retval != 0 ) {
        return retval;
    }

    /* call actual stat func */
    KASSERT(file_vnode->vn_ops->stat);
    retval = file_vnode->vn_ops->stat(file_vnode, buf);
    vput(file_vnode);

    return retval;
/* Yu code above v0.9 */
}

#ifdef __MOUNTING__
/*
 * Implementing this function is not required and strongly discouraged unless
 * you are absolutely sure your Weenix is perfect.
 *
 * This is the syscall entry point into vfs for mounting. You will need to
 * create the fs_t struct and populate its fs_dev and fs_type fields before
 * calling vfs's mountfunc(). mountfunc() will use the fields you populated
 * in order to determine which underlying filesystem's mount function should
 * be run, then it will finish setting up the fs_t struct. At this point you
 * have a fully functioning file system, however it is not mounted on the
 * virtual file system, you will need to call vfs_mount to do this.
 *
 * There are lots of things which can go wrong here. Make sure you have good
 * error handling. Remember the fs_dev and fs_type buffers have limited size
 * so you should not write arbitrary length strings to them.
 */
int
do_mount(const char *source, const char *target, const char *type)
{
        NOT_YET_IMPLEMENTED("MOUNTING: do_mount");
        return -EINVAL;
}

/*
 * Implementing this function is not required and strongly discouraged unless
 * you are absolutley sure your Weenix is perfect.
 *
 * This function delegates all of the real work to vfs_umount. You should not worry
 * about freeing the fs_t struct here, that is done in vfs_umount. All this function
 * does is figure out which file system to pass to vfs_umount and do good error
 * checking.
 */
int
do_umount(const char *target)
{
        NOT_YET_IMPLEMENTED("MOUNTING: do_umount");
        return -EINVAL;
}
#endif
